/**
 * Copyright © 2011 looty - Textoo
 * @link http://www.textoo.org
 * @link http://code.google.com/p/textoo/
 * @author lab9 - Bertrand Larrieu
 * @mail lab9.fr@gmail.com
 * @version 2.0
 */

package org.looty.textoo 
{
	import flash.display.*;
	import flash.events.*;
	import flash.utils.*;
	import org.looty.*;
	import org.looty.core.textoo.context.local.*;
	import org.looty.core.textoo.format.*;
	import org.looty.core.textoo.render.*;
	import org.looty.interactive.IMouseInteractive;
	import org.looty.textoo.context.*;
	import org.looty.textoo.events.*;
	import org.looty.textoo.render.*;
	
	public class Textoo extends Sprite
	{
		private var _isAddedToStage				:Boolean;
		private var _isAppended					:Boolean;
		private var _key						:String;
		private var _state						:uint;
		private var _smoothing					:Boolean;
		
		private var _localisationText			:LocalisationText;
		private var _context					:ITextooContext;		
		private var _link						:Textoo;		
		private var _props						:TextooProps;
		private var _renderData					:RenderData;
		
		
		public function Textoo(width:int = 0, height:int = 0, key:String = "", context:ITextooContext = null) 
		{
			_textoos.fixed = false;
			_textoos[_textoos.length] = this;
			_textoos.fixed = true;
			
			_props = new TextooProps(width, height);
			addEventListeners(_props, handleEvent, PROPS_EVENT_TYPES);
			
			this.context = context;
			
			initialise();
			
			this.key = key;
		}
		
		private function initialise():void 
		{
			addEventListener(Event.ADDED_TO_STAGE, addedToStage);
			addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage);
		}
		
		private function addedToStage(e:Event):void 
		{
			if (!Looty.isInitialised) Looty.initialise(stage);
			_isAddedToStage = true;
			if (_state != 0 && isReady) update();
		}
		
		private function removedFromStage(e:Event):void 
		{
			_isAddedToStage = false;
		}
		
		protected function get isReady():Boolean
		{
			return context != null && _isAddedToStage && _props.isReady;
		}
		
		protected function get isAppendable():Boolean
		{
			return isReady && !_isAppended;
		}
		
		private function handleEvent(e:TextooEvent):void 
		{
			switch(e.type)
			{
				case TextooEvent.SET_CONTEXT:
					retrieveLocalisationText();
				break;
				
				case TextooEvent.LOCALISATION_ADDED:
					if (_localisationText == null) retrieveLocalisationText();
				break;
				
				case TextooEvent.LOCALISATION_UPDATE:
					if (context.language != "none") _props.setLocalisation(_localisationText.text, _localisationText.className);
				break;
				
				case TextooEvent.STYLESHEET_CHANGE:
				case TextooEvent.REQUIRE_FORMAT:
					setState(FORMAT);
					append(this);
				break;
				
				case TextooEvent.REQUIRE_RENDER:
					setState(RENDER);
					append(this);
				break;
			}
			
			dispatchEvent(e);
		}		
		
		private function setState(value:uint):void 
		{
			if (_state < value) _state = value;			
		}		
		
		public function update():void
		{
			_isAppended = false;
			
			switch(_state)
			{
				case FORMAT :
					context.format(_props);
					applyOverflow();					
				case RENDER :
					removeDisplayObjects();
					_renderData = context.render(_props);
					addDisplayObjects();
			}
			
			_state = 0;
			
			dispatchEvent(UPDATE_EVENT);			
		}
		
		private function addDisplayObjects():void 
		{
			if (_renderData != null) for each (var displayObject:DisplayObject in _renderData.displayObjects) if (displayObject != null && !contains(displayObject)) 
			{
				addChild(displayObject);
				if (isBitmap && _smoothing && displayObject is Bitmap) Bitmap(displayObject).smoothing = true;
			}
		}
		
		private function removeDisplayObjects():void 
		{
			if (_renderData != null) for each (var displayObject:DisplayObject in _renderData.displayObjects) if (displayObject != null && contains(displayObject)) removeChild(displayObject);
		}
		
		public function dispose():void
		{
			removeEventListeners(_props, handleEvent, PROPS_EVENT_TYPES);
			_props.dispose();
			_props = null;
			
			_textoos.fixed = false;
			_textoos.splice(_textoos.indexOf(this), 1);
			_textoos.fixed = true;
			
			removeEventListeners(_context, handleEvent, CONTEXT_EVENT_TYPES);
			removeEventListeners(_localisationText, handleEvent, LOCALISATION_EVENT_TYPES);
		}
		
		public function link(textoo:Textoo):void 
		{
			if (_link == textoo) return;
			applyOverflow();
			_link = textoo;
			applyOverflow();
		}
		
		private function applyOverflow():void 
		{
			if (_link == null) return;
			_link.fillOverflow(_props);
		}
		
		private function fillOverflow(props:TextooProps):void 
		{
			_props.fillOverflow(props);
		}
		
		override public function set x(value:Number):void 
		{
			super.x = (value > 0 ? value + .5 : value - .5) | 0;
		}
		
		override public function set y(value:Number):void 
		{
			super.y = (value > 0 ? value + .5 : value - .5) | 0;
		}
		
		public function get displayObjects():Vector.<DisplayObject>
		{
			return _renderData != null ? _renderData.displayObjects : new Vector.<DisplayObject>();
		}
		
		public function get letters():Vector.<ILetter>
		{
			return _renderData != null ? _renderData.letters : new Vector.<ILetter>();
		}
		
		public function get interactions():Vector.<IMouseInteractive>
		{
			return _renderData != null ? _renderData.interactions : new Vector.<IMouseInteractive>();
		}
		
		public function get isBitmap():Boolean 
		{
			return _props.isBitmap;
		}
		
		public function set isBitmap(value:Boolean):void 
		{
			_props.isBitmap = value;
		}
		
		public function get isSplitted():Boolean 
		{
			return _props.isSplitted;
		}
		
		public function set isSplitted(value:Boolean):void 
		{
			_props.isSplitted = value;
		}		
		
		public function get smoothing():Boolean 
		{
			return _smoothing;
		}
		
		public function set smoothing(value:Boolean):void 
		{
			_smoothing = value;
			applySmoothing();
		}
		
		private function applySmoothing():void 
		{
			if (!isBitmap) return;
			
			for each (var displayObject:DisplayObject in _renderData.displayObjects) if (displayObject is Bitmap) Bitmap(displayObject).smoothing = _smoothing;
		}		
		
		public function set context(value:ITextooContext):void 
		{
			removeEventListeners(_context, handleEvent, CONTEXT_EVENT_TYPES);
			_context = value;
			addEventListeners(_context, handleEvent, CONTEXT_EVENT_TYPES);
			
			retrieveLocalisationText();
		}
		
		public function get context():ITextooContext 
		{
			return _context != null ? _context : _globalContext;
		}
		
		public function get key():String 
		{
			return _key;
		}
		
		public function set key(value:String):void 
		{
			_key = value;
			retrieveLocalisationText();
		}
		
		private function retrieveLocalisationText():void
		{
			if (_key == null || _key.length == 0 || context == null) return;
			
			var locText:LocalisationText = context.getText(_key);
			
			if (_localisationText == locText) return;
			
			removeEventListeners(_localisationText, handleEvent, LOCALISATION_EVENT_TYPES);
			_localisationText = locText;
			addEventListeners(_localisationText, handleEvent, LOCALISATION_EVENT_TYPES);
			
			if (_localisationText != null && context.language != "none") _props.setLocalisation(_localisationText.text, _localisationText.className);			
		}
		
		public function get localisationText():LocalisationText 
		{
			return _localisationText;
		}
		
		/*************************************************************************************************************
		 * encapsulated properties
		 *************************************************************************************************************/		
		
		
		public function get overflowIndex():uint { return _props.overflowIndex; }
		
		public function get isOverflowing():Boolean { return _props.isOverflowing; }
		
		public function get richTextOverflow():String { return _props.richTextOverflow; }
		
		public function get maxLines():uint { return _props.maxLines; }
		
		public function set maxLines(value:uint):void 
		{
			_props.setMaxLines(value);
		}
		
		public function get length():uint { return _props.length; }
		
		public function setTextVar(name:String, value:String):void
		{
			_props.setTextVar(name, value);
		}
		
		override public function get width():Number 
		{
			return _props.size.width;
		}
		
		override public function set width(value:Number):void 
		{
			_props.setWidth(value);
		}
		
		override public function get height():Number 
		{
			return _props.size.height;
		}
		
		override public function set height(value:Number):void 
		{
			_props.setHeight(value);
		}
		
		public function get autoSizeDirection():String 
		{
			return _props.autoSizeDirection;
		}
		
		public function set autoSizeDirection(value:String):void 
		{
			_props.setAutoSizeDirection(value);
		}
		
		public function get htmlText():String
		{
			return _props.htmlText;
		}
		
		public function get text():String
		{
			return _props.text;
		}
		
		public function set text(value:String):void
		{
			_props.setText(value);
		}
		
		public function get className():String
		{
			return _props.className;
		}
		
		public function set className(value:String):void
		{
			_props.setClassName(value);
		}
		
		public function get useDeviceFonts():Boolean { return _props.useDeviceFonts; }
		
		public function set useDeviceFonts(value:Boolean):void 
		{
			_props.setUseDeviceFonts(value);
		}
		
		public function get textColorType():String { return _props.textColorType; }
		
		public function set textColorType(value:String):void 
		{
			_props.setTextColorType(value);
		}
		
		public function get sharpness():Number { return _props.sharpness;	}
		
		public function set sharpness(value:Number):void 
		{
			_props.setSharpness(value);
		}
		
		public function get thickness():Number 
		{
			return _props.thickness;
		}
		
		public function set thickness(value:Number):void 
		{
			_props.setThickness(value);
		}
		
		public function get bitmapLetterClass():Class 
		{
			return _props.bitmapLetterClass;
		}
		
		public function set bitmapLetterClass(value:Class):void 
		{
			_props.setBitmapLetterClass(value);
		}
		
		public function get textFieldLetterClass():Class 
		{
			return _props.textFieldLetterClass;
		}
		
		public function set textFieldLetterClass(value:Class):void 
		{
			_props.setTextFieldLetterClass(value);
		}
		
		/*************************************************************************************************************
		 * statics
		 *************************************************************************************************************/
		static private const FORMAT						:uint = 2;
		static private const RENDER						:uint = 1;
		static private const CONTEXT_EVENT_TYPES		:Vector.<String> = Vector.<String>([TextooEvent.SET_CONTEXT, TextooEvent.LANGUAGE_ADDED, TextooEvent.REQUIRE_FORMAT, TextooEvent.REQUIRE_RENDER]);
		static private const PROPS_EVENT_TYPES			:Vector.<String> = Vector.<String>([TextooEvent.REQUIRE_FORMAT, TextooEvent.REQUIRE_RENDER]);
		static private const LOCALISATION_EVENT_TYPES	:Vector.<String> = Vector.<String>([TextooEvent.LOCALISATION_UPDATE]);
		
		static private const UPDATE_EVENT				:TextooEvent = new TextooEvent(TextooEvent.UPDATE);
		static private const SET_CONTEXT_EVENT			:TextooEvent = new TextooEvent(TextooEvent.SET_CONTEXT);
		
		static private var _globalContext				:ITextooContext;
		static private var _textoos						:Vector.<Textoo>;		
		static private var _queue						:Vector.<Textoo>;
		
		static internal function boot():void
		{
			_textoos = new Vector.<Textoo>(0, true);
			_queue = new Vector.<Textoo>();
			Looty.addEnterFrame(enterFrame);
		}
		
		static public function defineGlobalContext(context:ITextooContext):void
		{
			removeEventListeners(_globalContext, globalDispatch, CONTEXT_EVENT_TYPES);
			_globalContext = context;
			addEventListeners(_globalContext, globalDispatch, CONTEXT_EVENT_TYPES);
			_globalContext.dispatchEvent(SET_CONTEXT_EVENT);
		}
		
		static private function append(textoo:Textoo):void
		{
			if (!textoo.isAppendable) return;			
			textoo._isAppended = true;
			_queue.push(textoo);
		}
		
		static private function enterFrame():void 
		{
			for each (var textoo:Textoo in _queue) if (textoo != null) textoo.update();
			
			_queue.length = 0;
		}
		
		static private function addEventListeners(dispatcher:IEventDispatcher, closure:Function, events:Vector.<String>):void
		{
			if (dispatcher == null || closure == null) return;
			for each (var type:String in events) dispatcher.addEventListener(type, closure);
		}
		
		static private function removeEventListeners(dispatcher:IEventDispatcher, closure:Function, events:Vector.<String>):void
		{
			if (dispatcher == null || closure == null) return;
			for each (var type:String in events) if (dispatcher.hasEventListener(type)) dispatcher.removeEventListener(type, closure);
		}
		
		static private function globalDispatch(e:TextooEvent):void 
		{
			for each (var textoo:Textoo in _textoos) if (textoo._context == null) textoo.handleEvent(e);
		}	
		
	}
	
	org.looty.textoo.Textoo.boot();
	
}

